// VS_BEGIN
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aCoord;
layout (location = 2) in vec3 aNormal;

uniform mat4 u_model;

layout (std140) uniform EngineUBO
{
    mat4 u_view;
    mat4 u_project;
    vec3 u_camPos;
};

out G_DATA {
    vec3 worldPos;
    vec3 normal;
    vec2 coord;
} vs_out;


void main() {
    vs_out.worldPos = vec3(u_model * vec4(aPos, 1.0f));
    vs_out.normal = normalize(vec3(u_model * vec4(aNormal, 0.0f)));
    vs_out.coord = aCoord;
    gl_Position = u_project * u_view * vec4(vs_out.worldPos, 1.0f);
}
// VS_END

// FS_BEGIN
#version 330 core

in G_DATA {
    vec3 worldPos;
    vec3 normal;
    vec2 coord;
} gs_out;

out vec4 FragColor;

layout (std140) uniform EngineUBO
{
    mat4 u_view;
    mat4 u_project;
    vec3 u_camPos;
};

layout (std140) uniform LightUBO
{
    mat4 u_lights[64];
    int u_lightNum;
};

uniform sampler2D   u_diffuseMap;
uniform sampler2D   u_specularMap;
uniform vec4        u_diffuse           = vec4(1.0f, 1.0f, 1.0f, 1.0f);
uniform vec3        u_specular          = vec3(1.0f, 1.0f, 1.0f);
uniform float       u_shininess         = 100.0f;    


vec4 g_diffuseTexel;
vec4 g_specularTexel;
vec3 g_viewDir;

vec3 BlinnPhong(vec3 lightDir, vec3 lightColor, float lightStrength) {
    vec3 halfDir = normalize(lightDir + g_viewDir);
    float diffuseFactor = max(dot(lightDir, gs_out.normal), 0.0f);
    float specularFactor = pow(max(dot(gs_out.normal, halfDir), 0.0f), u_shininess * 2.0f);
    return (vec3(g_diffuseTexel) * diffuseFactor + vec3(g_specularTexel) * specularFactor) * lightColor * lightStrength;
}

vec3 PointLight(mat4 light) {
    vec3 lPos = light[0].xyz;
    vec3 lColor = light[1].xyz;
    float lStrength = light[1].w;
    vec3 dir = normalize(lPos - gs_out.worldPos);
    float dis = distance(lPos, gs_out.worldPos);
    return BlinnPhong(dir, lColor, max(lStrength / dis, 0.0f));
}

vec3 DirectLight(mat4 light) {
    vec3 ldir = normalize(light[2].xyz);
    vec3 lColor = light[1].xyz;
    float lStrength = light[1].w;
    return BlinnPhong(-ldir, lColor, lStrength);
}

vec3 AmbientSphereLight(mat4 light) {
    vec3 lPos = light[0].xyz;
    vec3 lColor = light[1].xyz;
    float lStrength = light[1].w;
    float lradius = light[2].x;
    float dis = distance(lPos, gs_out.worldPos);
    if (dis <= lradius) {
        return vec3(g_diffuseTexel) * lColor * lStrength;
    }
    return vec3(0.0, 0.0, 0.0);
}

vec4 MixTexture(vec4 texel, vec4 color) {
    if (texel.rgb != vec3(0.0f)) {
        return texel * color;
    }
    return color;
}

void main() {
    g_diffuseTexel = MixTexture(texture(u_diffuseMap, gs_out.coord), u_diffuse);
    g_specularTexel = MixTexture(texture(u_specularMap, gs_out.coord), vec4(u_specular, 1.0f)); 
    g_viewDir = normalize(u_camPos - gs_out.worldPos);
    vec3 lightSum = vec3(0.0f);
    for (int i = 0; i < u_lightNum; ++i) {
        switch (int(u_lights[i][0].w)) {
        case 0: lightSum += PointLight(u_lights[i]); break;
        case 1: lightSum += DirectLight(u_lights[i]); break;
        case 2: lightSum += AmbientSphereLight(u_lights[i]); break;
        }
    }
    FragColor = vec4(lightSum, u_diffuse.a);
}
// FS_END